package com.hsu.yupao.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.hsu.yupao.common.ErrorCode;
import com.hsu.yupao.constant.FriendConstant;
import com.hsu.yupao.exception.BusinessException;
import com.hsu.yupao.model.domain.Friend;
import com.hsu.yupao.model.domain.User;
import com.hsu.yupao.model.request.FriendAddRequest;
import com.hsu.yupao.model.vo.FriendRecordsVO;
import com.hsu.yupao.service.FriendService;
import com.hsu.yupao.mapper.FriendMapper;
import com.hsu.yupao.service.UserService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.hsu.yupao.utils.StringUtils.stringJsonListToLongSet;

/**
 * @author 幽琴健
 * @description 针对表【friend(好友申请表)】的数据库操作Service实现
 * @createDate 2024-06-02 15:42:03
 */
@Service
public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend>
        implements FriendService {

    @Resource
    private FriendMapper friendMapper;

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;

    /**
     * 添加好友的业务方法。
     * 处理用户发送好友申请的逻辑，包括校验申请信息、检查是否已存在相同的未处理申请、以及保存好友申请。
     * 使用Redisson锁来确保并发环境下的数据一致性。
     * @param friendAddRequest 好友添加请求对象，包含被申请人ID和附言
     * @param loginUser 当前登录的用户，即好友申请的发送者
     * @return boolean 如果添加成功返回true，否则返回false
     * @throws BusinessException 如果业务规则不满足或操作中断，抛出异常
     */
    @Override
    public boolean addFriend(FriendAddRequest friendAddRequest, User loginUser) {
        // 校验申请附言是否过长
        if (StringUtils.isNotBlank(friendAddRequest.getRequestMessage()) && friendAddRequest.getRequestMessage().length() > 100) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "申请附言字数不能大于100个字符");
        }
        // 检查是否尝试添加自己为好友
        if (friendAddRequest.getToUserId().equals(loginUser.getId())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "不能添加自己为好友");
        }
        // 检查是否已经是好友
        Set<Long> friendIds = stringJsonListToLongSet(loginUser.getUserIds());
        if (friendIds.contains(friendAddRequest.getToUserId())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "你们已经是好友");
        }
        // 使用Redisson分布式锁确保添加操作的原子性
        RLock lock = redissonClient.getLock("youyuan:add_friend");
        try {
            // 尝试获取锁，无等待
            if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(Friend::getFromUserId, loginUser.getId())
                        .eq(Friend::getToUserId, friendAddRequest.getToUserId());
                List<Friend> existingRequests = friendMapper.selectList(queryWrapper);
                // 检查是否已存在未处理的好友申请
                if (!existingRequests.isEmpty() && existingRequests.stream().anyMatch(f -> f.getStatus() == 0)) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "已存在好友申请");
                }
                // 创建并保存新的好友请求
                Friend friend = new Friend();
                friend.setFromUserId(loginUser.getId());
                friend.setToUserId(friendAddRequest.getToUserId());
                if (StringUtils.isBlank(friendAddRequest.getRequestMessage())) {
                    friend.setRequestMessage("我是" + userService.getById(loginUser.getId()).getUserName());
                } else {
                    friend.setRequestMessage(friendAddRequest.getRequestMessage());
                }
                return this.save(friend);
            } else {
                return false;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("addFriend error", e);
            return false;
        } finally {
            // 最终块确保只有持有锁的线程能够释放锁
            if (lock.isHeldByCurrentThread()) {
                // 输出当前释放锁的线程ID，用于调试和监控
                System.out.println("unlock: " + Thread.currentThread().getId());
                lock.unlock();
            }
        }
    }

    /**
     * 获取指定用户收到的所有好友申请记录。
     * @param loginUser 当前登录用户，用于确定查询哪些好友申请。
     * @return List<FriendRecordsVO> 包含转换后的好友申请记录视图对象列表。
     */
    @Override
    public List<FriendRecordsVO> getFriendRecords(User loginUser) {
        // 创建查询条件，筛选当前登录用户作为接收者的好友申请
        LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Friend::getToUserId, loginUser.getId());
        List<Friend> friendList = this.list(queryWrapper); // 根据条件查询好友申请列表
        // 将好友申请数据转换为视图对象列表
        Collections.reverse(friendList); // 将列表反转，使最新的记录排在前面
        return friendList.stream().map(friend -> {
            FriendRecordsVO friendRecordsVO = new FriendRecordsVO(); // 创建视图对象
            BeanUtils.copyProperties(friend, friendRecordsVO); // 复制属性到视图对象
            User user = userService.getById(friend.getFromUserId()); // 获取被申请人信息
            friendRecordsVO.setApplyUser(userService.getSafetyUser(user)); // 设置视图对象中脱敏后的申请人信息
            return friendRecordsVO;
        }).collect(Collectors.toList()); // 收集转换后的视图对象列表
    }

    /**
     * 获取当前用户发出的好友申请记录。
     * 此方法检索当前登录用户作为申请人的所有好友申请，用于显示用户自己发起的申请状态。
     * @param loginUser 当前登录的用户，即好友申请的发起者。
     * @return List<FriendRecordsVO> 包含转换后的好友申请记录视图对象列表。
     */
    @Override
    public List<FriendRecordsVO> getMyRecords(User loginUser) {
        // 创建查询条件，筛选当前登录用户作为接收者的好友申请
        LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Friend::getFromUserId, loginUser.getId());
        List<Friend> friendList = this.list(queryWrapper); // 根据条件查询好友申请列表
        // 将好友申请数据转换为视图对象列表
        Collections.reverse(friendList); // 将列表反转，使最新的记录排在前面
        return friendList.stream().map(friend -> {
            FriendRecordsVO friendRecordsVO = new FriendRecordsVO(); // 创建视图对象
            BeanUtils.copyProperties(friend, friendRecordsVO); // 复制属性到视图对象
            User user = userService.getById(friend.getToUserId()); // 获取被申请人信息
            friendRecordsVO.setApplyUser(userService.getSafetyUser(user)); // 设置视图对象中脱敏后的申请人信息
            return friendRecordsVO;
        }).collect(Collectors.toList()); // 收集转换后的视图对象列表
    }

    /**
     * 获取当前用户未读且未处理的好友申请数量。
     * @param loginUser 当前登录的用户
     * @return 返回未读且未处理的好友申请的数量
     */
    @Override
    public long getUnreadRecordCount(User loginUser) {
        LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Friend::getToUserId, loginUser.getId())  // 限定查询到当前用户接收的好友申请
                .eq(Friend::getIsRead, FriendConstant.NOT_READ)  // 筛选出未读的记录
                .eq(Friend::getStatus, FriendConstant.DEFAULT_STATUS);  // 只考虑未处理的申请
        return this.count(queryWrapper);  // 返回计数结果
    }

    /**
     * 将指定的好友申请标记为已读，仅限于未处理且未读的记录。
     * @param loginUser 当前登录的用户
     * @param ids 需要标记为已读的好友申请ID集合
     * @return 如果更新成功返回true，否则返回false
     */
    @Override
    @Transactional(rollbackFor = Exception.class)  // 确保操作的原子性，失败时回滚
    public boolean markAsRead(User loginUser, Set<Long> ids) {
        LambdaUpdateWrapper<Friend> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Friend::getIsRead, FriendConstant.READ)  // 设置记录为已读
                .in(Friend::getId, ids)  // 指定需要更新的记录ID
                .eq(Friend::getToUserId, loginUser.getId())  // 确保这些记录属于当前登录用户
                .eq(Friend::getStatus, FriendConstant.DEFAULT_STATUS)  // 仅更新未处理的申请
                .eq(Friend::getIsRead, FriendConstant.NOT_READ);  // 确保记录当前是未读状态
        return this.update(updateWrapper);  // 执行更新操作并返回结果
    }

    /**
     * 同意好友申请的业务逻辑处理。
     * 此方法检查针对当前登录用户的好友申请，并在符合条件的情况下更新申请状态为已同意。
     * 过程中确保申请未过期，并更新双方用户的好友列表。
     * @param loginUser 当前登录的用户，代表好友申请的接收方。
     * @param fromUserid 发起申请的用户ID，即申请的发送方。
     * @return boolean 返回操作是否成功。如果用户信息更新成功且申请状态成功更新为已同意，则返回true，否则返回false。
     * @throws BusinessException 如果没有找到符合条件的申请、存在多个未处理申请、或申请已过期，则抛出业务异常。
     */
    @Override
    public boolean agreeToApply(User loginUser, Long fromUserid) {
        // 查询当前登录用户作为接收者，指定用户作为申请者的所有未处理的好友申请
        LambdaQueryWrapper<Friend> friendQueryWrapper = new LambdaQueryWrapper<>();
        friendQueryWrapper.eq(Friend::getToUserId, loginUser.getId())
                .eq(Friend::getFromUserId, fromUserid)
                .eq(Friend::getStatus, FriendConstant.DEFAULT_STATUS);
        List<Friend> applications = this.list(friendQueryWrapper);
        // 过滤出所有有效的未处理申请
        List<Friend> validApplications = applications.stream()
                .filter(app -> app.getStatus() == FriendConstant.DEFAULT_STATUS)
                .collect(Collectors.toList());
        // 如果没有有效的申请或存在多个有效申请
        if (validApplications.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "该申请不存在或已处理");
        }
        if (validApplications.size() > 1) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "存在多个相同的未处理申请");
        }
        // 获取单个申请进行处理
        Friend application = validApplications.get(0);
        // 检查申请是否已过期（3天后过期）
        if (new Date().getTime() - application.getCreateTime().getTime() > TimeUnit.DAYS.toMillis(3)) {
            application.setStatus(FriendConstant.EXPIRED_STATUS);
            this.updateById(application);
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "该申请已过期");
        }
        // 更新用户的好友列表
        User fromUser = userService.getById(fromUserid);
        User toUser = userService.getById(loginUser.getId());
        Set<Long> toUserFriendIds = stringJsonListToLongSet(fromUser.getUserIds());
        Set<Long> fromUserFriendIds = stringJsonListToLongSet(fromUser.getUserIds());
        fromUserFriendIds.add(toUser.getId());
        toUserFriendIds.add(fromUser.getId());
        // 将更新后的好友列表转换回字符串并保存
        Gson gson = new Gson();
        fromUser.setUserIds(gson.toJson(fromUserFriendIds));
        toUser.setUserIds(gson.toJson(toUserFriendIds));
        // 更新用户信息
        boolean updateUserSuccess = userService.updateById(fromUser) && userService.updateById(toUser);
        // 将申请状态设置为已同意
        application.setStatus(FriendConstant.AGREE_STATUS);
        // 更新好友申请信息
        boolean updateApplicationSuccess = this.updateById(application);
        return updateUserSuccess && updateApplicationSuccess;
    }

    /**
     * 撤销好友申请。
     * 此方法允许用户撤回之前发出的尚未被处理（即未接受或未拒绝）的好友申请。
     * 只有当申请存在且处于未处理状态时，才能执行撤销操作。
     * @param id        申请记录的ID，用来唯一标识一个好友申请。
     * @param loginUser 当前登录的用户，用于验证操作权限，确保是申请人才可以撤销。
     * @return boolean  返回操作结果，成功撤销返回true，否则返回false。
     * @throws BusinessException 如果请求的申请不存在、用户不是申请人或申请状态不允许撤销，则抛出业务异常。
     */
    @Override
    public boolean cancelApply(Long id, User loginUser) {
        // 获取指定ID的好友申请记录
        Friend friend=this.getById(id);
        // 验证申请是否存在、是否属于登录用户且状态为默认（未处理）
        if(friend==null||!friend.getFromUserId().equals(loginUser.getId())||friend.getStatus()!=FriendConstant.DEFAULT_STATUS){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "无法撤销：申请不存在或状态不允许");
        }
        // 设置申请状态为已撤销
        friend.setStatus(FriendConstant.REVOKE_STATUS);
        return this.updateById(friend);
    }

}
